﻿// Template for creating a OpenCollar Plugin - OpenCollar Version 3.4+
// Inworld version for SVN storage
// Version: 3.400
// Date: 2009/12/15
// Last edited by: mantel.jeff

//Licensed under the GPLv2, with the additional requirement that these scripts remain "full perms" in Second Life.  See "OpenCollar License" for details.

// Please remove any unneeded code sections to save memory and sim time


string g_sSubmenu = "Plugin"; // Name of the submenu
string g_sParentmenu = "AddOns"; // name of the menu, where the menu plugs in, should be usually Addons. Please do not use the mainmenu anymore
string g_sChatCommand = "plugin"; // every menu should have a chat command, so the user can easily access it by type for instance *plugin
key g_kMenuID;  // menu handler
integer g_iDebugMode=FALSE; // set to TRUE to enable Debug messages

key g_kWearer; // key of the current wearer to reset only on owner changes

integer g_iReshowMenu=FALSE; // some command need to wait on a processing or need to run through the auth sstem before they can show a menu again, they can use the variable and call the menu if it is set to true


list g_lLocalbuttons = ["Command 1","Command 2","AuthCommand"]; // any local, not changing buttons which will be used in this plugin, leave empty or add buttons as you like

list g_lButtons;

//OpenCollar MESSAGE MAP
integer JSON_REQUEST = 201;
integer JSON_RESPONSE = 202;

// messages for authenticating users
integer COMMAND_NOAUTH = 0;
integer COMMAND_COLLAR = 499; //added for collar or cuff commands to put ao to pause or standOff
integer COMMAND_OWNER = 500;
integer COMMAND_SECOWNER = 501;
integer COMMAND_GROUP = 502;
integer COMMAND_WEARER = 503;
integer COMMAND_EVERYONE = 504;
//integer CHAT = 505;//deprecated
integer COMMAND_OBJECT = 506;
integer COMMAND_RLV_RELAY = 507;
integer COMMAND_SAFEWORD = 510;
integer COMMAND_RELAY_SAFEWORD = 511;
integer COMMAND_BLACKLIST = 520;
// added for timer so when the sub is locked out they can use postions
integer COMMAND_WEARERLOCKEDOUT = 521;

integer ATTACHMENT_REQUEST = 600;
integer ATTACHMENT_RESPONSE = 601;
integer ATTACHMENT_FORWARD = 610;

//CONFLIT????
integer TIMER_TOMESSAGE=609;
integer TIMER_FROMMESSAGE=610;

integer WEARERLOCKOUT=620;//turns on and off wearer lockout

//integer SEND_IM = 1000; deprecated.  each script should send its own IMs now.  This is to reduce even the tiny bit of lag caused by having IM slave scripts
integer POPUP_HELP = 1001;

// messages for storing and retrieving values from http db
integer HTTPDB_SAVE = 2000;//scripts send messages on this channel to have settings saved to httpdb
//str must be in form of "token=value"
integer HTTPDB_REQUEST = 2001;//when startup, scripts send requests for settings on this channel
integer HTTPDB_RESPONSE = 2002;//the httpdb script will send responses on this channel
integer HTTPDB_DELETE = 2003;//delete token from DB
integer HTTPDB_EMPTY = 2004;//sent by httpdb script when a token has no value in the db
integer HTTPDB_REQUEST_NOCACHE = 2005;

// same as HTTPDB_*, but for storing settings locally in the settings script
integer LOCALSETTING_SAVE = 2500;
integer LOCALSETTING_REQUEST = 2501;
integer LOCALSETTING_RESPONSE = 2502;
integer LOCALSETTING_DELETE = 2503;
integer LOCALSETTING_EMPTY = 2504;


// messages for creating OC menu structure
integer MENUNAME_REQUEST = 3000;
integer MENUNAME_RESPONSE = 3001;
integer SUBMENU = 3002;
integer MENUNAME_REMOVE = 3003;

// OOCD stuff
integer COMMAND_OCCD_BITS = -4077;  // tells the bits to change state
integer COMMAND_OCCD_STATE = -4078; // tells the OCCD module what state the user sent to the bits
integer COMMAND_OCCD_ALLOW_HIDE = -4079; // tells the OCCD bits module that its allowed to hide the bits when the wearer asks

//5000 block is reserved for IM slaves//no longer in use

// messages for RLV commands
integer RLV_CMD = 6000;
integer RLV_REFRESH = 6001;//RLV plugins should reinstate their restrictions upon receiving this message.
integer RLV_CLEAR = 6002;//RLV plugins should clear their restriction lists upon receiving this message.
integer RLV_VERSION = 6003; //RLV Plugins can recieve the used rl viewer version upon receiving this message..

integer RLVR_CMD = 6010;

integer RLV_OFF = 6100; // send to inform plugins that RLV is disabled now, no message or key needed
integer RLV_ON = 6101; // send to inform plugins that RLV is enabled now, no message or key needed

// messages for poses and couple anims
integer ANIM_START = 7000;//send this with the name of an anim in the string part of the message to play the anim
integer ANIM_STOP = 7001;//send this with the name of an anim in the string part of the message to stop the anim
integer CPLANIM_PERMREQUEST = 7002;//id should be av's key, str should be cmd name "hug", "kiss", etc
integer CPLANIM_PERMRESPONSE = 7003;//str should be "1" for got perms or "0" for not.  id should be av's key
integer CPLANIM_START = 7004;//str should be valid anim name.  id should be av
integer CPLANIM_STOP = 7005;//str should be valid anim name.  id should be av

// messages to the dialog helper
integer DIALOG = -9000;
integer DIALOG_RESPONSE = -9001;
integer DIALOG_TIMEOUT = -9002;

integer TIMER_EVENT = -10000; // str = "start" or "end". For start, either "online" or "realtime".

integer UPDATE = 10001;//for hovertext to get ready?

// For other things that want to manage showing/hiding keys.
integer KEY_VISIBLE = -10100;
integer KEY_INVISIBLE = -10100;


integer COMMAND_PARTICLE = 20000;
integer COMMAND_LEASH_SENSOR = 20001;

//chain systems
integer LOCKMEISTER         = -8888;
integer LOCKGUARD           = -9119;
//rlv relay chan
integer g_iRlvChan = -1812221819;


// menu option to go one step back in menustructure
string UPMENU = "^";//when your menu hears this, give the parent menu

//===============================================================================
//= parameters   :    string    sMsg    message string received
//=
//= return        :    none
//=
//= description  :    output debug messages
//=
//===============================================================================


Debug(string sMsg)
{
	if (!g_iDebugMode) return;
	llOwnerSay(llGetScriptName() + ": " + sMsg);
}

//===============================================================================
//= parameters   :    key       kID                key of the avatar that receives the message
//=                   string    sMsg               message to send
//=                   integer   iAlsoNotifyWearer  if TRUE, a copy of the message is sent to the wearer
//=
//= return        :    none
//=
//= description  :    notify targeted id and maybe the wearer
//=
//===============================================================================

Notify(key kID, string sMsg, integer iAlsoNotifyWearer)
{
	if (kID == g_kWearer)
	{
		llOwnerSay(sMsg);
	}
	else
	{
		llInstantMessage(kID,sMsg);
		if (iAlsoNotifyWearer)
		{
			llOwnerSay(sMsg);
		}
	}
}

//===============================================================================
//= parameters   :    none
//=
//= return        :    key random uuid
//=
//= description  :    random key generator, not complety unique, but enough for use in dialogs
//=
//===============================================================================


key ShortKey()
{//just pick 8 random hex digits and pad the rest with 0.  Good enough for dialog uniqueness.
	string sChars = "0123456789abcdef";
	integer iLength = 16;
	string sOut;
	integer n;
	for (n = 0; n < 8; n++)
	{
		integer iIndex = (integer)llFrand(16);//yes this is correct; an integer cast rounds towards 0.  See the llFrand wiki entry.
		sOut += llGetSubString(sChars, iIndex, iIndex);
	}

	return (key)(sOut + "-0000-0000-0000-000000000000");
}

//===============================================================================
//= parameters   :    key   kRCPT  recipient of the dialog
//=                   string  sPrompt    dialog prompt
//=                   list  lChoices    true dialog buttons
//=                   list  lUtilityButtons  utility buttons (kept on every iPage)
//=                   integer   iPage    Page to be display
//=
//= return        :    key  handler of the dialog
//=
//= description  :    displays a dialog to the given recipient
//=
//===============================================================================


key Dialog(key kRCPT, string sPrompt, list lChoices, list lUtilityButtons, integer iPage)
{
	key kID = ShortKey();
	llMessageLinked(LINK_SET, DIALOG, (string)kRCPT + "|" + sPrompt + "|" + (string)iPage + "|" + llDumpList2String(lChoices, "`") + "|" + llDumpList2String(lUtilityButtons, "`"), kID);
	return kID;
}

//===============================================================================
//= parameters   :    string    sMsg    message string received
//=
//= return        :    integer TRUE/FALSE
//=
//= description  :    checks if a string begin with another string
//=
//===============================================================================

integer nStartsWith(string sHaystack, string sNeedle) // http://wiki.secondlife.com/wiki/llSubStringIndex
{
	return (llDeleteSubString(sHaystack, llStringLength(sNeedle), -1) == sNeedle);
}

//===============================================================================
//= parameters   :    string    keyID   key of person requesting the menu
//=
//= return        :    none
//=
//= description  :    build menu and display to user
//=
//===============================================================================

DoMenu(key keyID)
{
	string sPrompt = "Pick an option.\n";
	list lMyButtons = g_lLocalbuttons + g_lButtons;

	//fill in your button list and additional prompt here
	lMyButtons = llListSort(lMyButtons, 1, TRUE); // resort menu buttons alphabetical

	// and dispay the menu
	g_kMenuID = Dialog(keyID, sPrompt, lMyButtons, [UPMENU], 0);
}


//===============================================================================
//= parameters   :    none
//=
//= return        :   string     DB prefix from the description of the collar
//=
//= description  :    prefix from the description of the collar
//=
//===============================================================================

string GetDBPrefix()
{//get db prefix from list in object desc
	return llList2String(llParseString2List(llGetObjectDesc(), ["~"], []), 2);
}



default
{
	state_entry()
	{
		// store key of wearer
		g_kWearer = llGetOwner();
		// sleep a second to allow all scripts to be initialized
		llSleep(1.0);
		// send request to main menu and ask other menus if they want to register with us
		llMessageLinked(LINK_THIS, MENUNAME_REQUEST, g_sSubmenu, NULL_KEY);
		llMessageLinked(LINK_THIS, MENUNAME_RESPONSE, g_sParentmenu + "|" + g_sSubmenu, NULL_KEY);
	}

	// reset the script if wearer changes. By only reseting on owner change we can keep most of our
	// configuration in the script itself as global variables, so that we don't loose anything in case
	// the httpdb server isn't available
	// Cleo: As per Nan this should be a reset on every rez, this has to be handled as needed, but be prepared that the user can reset your script anytime using the OC menus
	on_rez(integer iParam)
	{
		if (llGetOwner()!=g_kWearer)
		{
			// Reset if wearer changed
			llResetScript();
		}
	}


	// listen for linked messages from OC scripts
	link_message(integer iSender, integer iNum, string sStr, key kID)
	{
		if (iNum == SUBMENU && sStr == g_sSubmenu)
		{
			//someone asked for our menu
			//give this plugin's menu to id
			DoMenu(kID);
		}
		else if (iNum == MENUNAME_REQUEST && sStr == g_sParentmenu)
			// our parent menu requested to receive buttons, so send ours
		{

			llMessageLinked(LINK_THIS, MENUNAME_RESPONSE, g_sParentmenu + "|" + g_sSubmenu, NULL_KEY);
		}
		else if (iNum == MENUNAME_RESPONSE)
			// a button is send to be added to a menu
		{
			list lParts = llParseString2List(sStr, ["|"], []);
			if (llList2String(lParts, 0) == g_sSubmenu)
			{//someone wants to stick something in our menu
				string button = llList2String(lParts, 1);
				if (llListFindList(g_lButtons, [button]) == -1)
					// if the button isnt in our menu yet, than we add it
				{
					g_lButtons = llListSort(g_lButtons + [button], 1, TRUE);
				}
			}
		}
		else if (iNum == HTTPDB_RESPONSE)
			// response from httpdb have been received
		{
			// pares the answer
			list lParams = llParseString2List(sStr, ["="], []);
			string sToken = llList2String(lParams, 0);
			string sValue = llList2String(lParams, 1);
			// and check if any values for use are received
			// replace "value1" by your own token
			
			if (sToken == "value1" )
			{
				// work with the received values
			}
			// replace "value2" by your own token, but if possible try to store everything in one token
			// to reduce the load on the httpdb server
			else if (sToken == "value2")
			{
				// work with the received values
			}
			// or check for specific values from the collar like "owner" (for owners) "secowners" (or secondary owners) etc
			else if (sToken == "owner")
			{
				// work with the received values, in this case pare the vlaue into a strided list with the owners
				
				list lOwners = llParseString2List(sValue, [","], []);
			}
		}
		else if (iNum >= COMMAND_OWNER && iNum <= COMMAND_WEARER)
			// a validated command from a owner, secowner, groupmember or the wearer has been received
			// can also be used to listen to chat commands
		{
			list lParams = llParseString2List(sStr, [" "], []);
			string sCommand = llToLower(llList2String(lParams, 0));
			string sValue = llToLower(llList2String(lParams, 1));
				// So commands can accept a value
				if (sStr == "reset")
					// it is a request for a reset
				{
					if (iNum == COMMAND_WEARER || iNum == COMMAND_OWNER)
					{   //only owner and wearer may reset
						llResetScript();
					}
				}
			else if (sStr == g_sChatCommand)
				// an authorized user requested the plugin menu by typing the menus chat command
			{
				DoMenu(kID);
			}
			else if (sStr == "yourcommandhere")
				// example for a command which can be invoked by chat and/or menu, replace yourcommandhere by what you need
			{
				Debug ("Do some fancy stuff to impress the user");

				// maybe save a value to the db:
				llMessageLinked(LINK_THIS, HTTPDB_SAVE, "token=value", NULL_KEY);

				// or delete a toke from the db:
				llMessageLinked(LINK_THIS, HTTPDB_DELETE, "token", NULL_KEY);

				
				if (g_iReshowMenu)
					// the command has been called via the menu and wants the menu to pop up again
				{
					// reset the menu variable
					g_iReshowMenu=FALSE;
					// and show them menu again to the user
					DoMenu(kID);
				}
			}

		}
		else if (iNum == COMMAND_EVERYONE)
			// you might want to react on unauthorized users or such, see message map on the top of the script, please remove if not needed
		{
			Debug("Go away and get your own sub, you have no right on this collar");
		}

		else if (iNum == COMMAND_SAFEWORD)
			// Safeword has been received, release any restricitions that should be released
		{
			Debug("Safeword reveived, releasing the subs restricions as needed");
		}
		else if (iNum == DIALOG_RESPONSE)
			// answer from menu system
			// careful, don't use the variable kID to identify the user, it is the UUID we generated when calling the dialog
			// you have to parse the answer from the dialog system and use the parsed variable kAv
		{
			if (kID == g_kMenuID)
			{
				//got a menu response meant for us, extract the values
				list lMenuParams = llParseString2List(sStr, ["|"], []);
				key kAv = (key)llList2String(lMenuParams, 0);
				string sMessage = llList2String(lMenuParams, 1);
				integer iPage = (integer)llList2String(lMenuParams, 2);
				// request to switch to parent menu
				if (sMessage == UPMENU)
				{
					//give av the parent menu
					llMessageLinked(LINK_THIS, SUBMENU, g_sParentmenu, kAv);
				}
				else if (~llListFindList(g_lLocalbuttons, [sMessage]))
				{
					//we got a response for something we handle locally
					if (sMessage == "Command 1")
					{
						// do What has to be Done
						Debug("Command 1");
						// and restart the menu if wanted/needed
						DoMenu(kAv);
					}
					else if (sMessage == "Command 2")
					{
						// do What has to be Done
						Debug("Command 2");
						// and restart the menu if wanted/needed
						DoMenu(kAv);
					}
					else if (sMessage == "AuthCommand")
						// this command is an example of a command which needs to be run through the auth system or other stuff,
						// before it can show the menu again, so we use the g_iReshowMenu variable here
					{
						g_iReshowMenu=TRUE;
						// and send the commans through the Auth system of the collar.
						// We use in this case button name, which could be used as chat command as well
						llMessageLinked(LINK_SET, COMMAND_NOAUTH, "yourcommandhere", kAv);
						// now we lean back and let the collar do the work, the command will come back as a linked message
						// and through the auth variable we'll know if it comes from an owner, secowner, group member, wearer, or someone else.
					}
				}
				else if (~llListFindList(g_lButtons, [sMessage]))
				{
					//we got a button which another plugin put into into our menu
					llMessageLinked(LINK_THIS, SUBMENU, sMessage, kAv);
				}
			}
		}
		else if (iNum == DIALOG_TIMEOUT)
			// timeout from menu system, you do not have to react on this, but you can
		{
			if (kID == g_kMenuID)
				// if you react, make sure the timeout is from your menu by checking the g_kMenuID variable
			{
				Debug("The user was to slow or lazy, we got a timeout!");
			}
		}
	}

}

